package com.lwq.lwqmicoservice.gateway.service;

import com.auth0.jwt.interfaces.Claim;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.exceptions.ApiException;
import com.lwq.lwqmicoservice.common.exception.AuthenticationException;
import com.lwq.lwqmicoservice.common.exception.FieldException;
import com.lwq.lwqmicoservice.common.model.SysPermission;
import com.lwq.lwqmicoservice.common.model.SysUser;
import com.lwq.lwqmicoservice.common.model.TokenModel;
import com.lwq.lwqmicoservice.common.util.StringUtils;
import com.lwq.lwqmicoservice.common.util.TokenUtil;
import com.lwq.lwqmicoservice.framework.mapper.MenuMapper;
import com.lwq.lwqmicoservice.framework.mapper.UserMapper;
import com.lwq.lwqmicoservice.framework.service.MenuService;
import com.lwq.lwqmicoservice.framework.service.PermissionService;
import com.lwq.lwqmicoservice.framework.service.RoleService;
import com.lwq.lwqmicoservice.framework.util.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Description:
 * Author: LiuWenQing
 * Datetime: 2021/11/16
 */
@Service
public class AuthenticationService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PasswordEncoder pe;
    @Autowired
    private RedisUtil redis;
    @Value("${token.expire}")
    private int tokenExpire;
    @Value("${permission.administratorAccounts}")
    private String adminAccount;
    @Value("${token.excludePath}")
    private String excludePah;
    @Autowired
    private PermissionService permissionService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private MenuService menuService;
    @Autowired
    private MenuMapper menuMapper;

    @Transactional(rollbackFor = Exception.class)
    public TokenModel doLogin(SysUser user) {
        SysUser model = this.userMapper.selectOne(new QueryWrapper<SysUser>().eq("name", user.getName()));
        if (model == null) {
            throw new AuthenticationException("账号不存在");
        }
        if (model.getStatus().equals(SysUser.DISABLE)) {
            throw new AuthenticationException("账号已被禁用");
        }
        if (!this.pe.matches(user.getPassword(), model.getPassword())) {
            throw new AuthenticationException("账号或密码错误!");
        }
        TokenModel tk = null;
        try {
            String redisToken = redis.get(model.getId().toString());
            if (!StringUtils.isEmpty(redisToken)) {
                if (!StringUtils.isEmpty(model.getMultiLogin()) &&
                        model.getMultiLogin().equals(SysUser.BANNEDMULTILOGIN)) {
                    redis.del(model.getId().toString());
                    tk = this.createToken(model);
                } else {
                    tk = this.createToken(model, redisToken);
                }
            } else {
                tk = this.createToken(model);
            }
        } catch (Exception e) {
            throw new RuntimeException("创建token异常");
        }
        redis.put(model.getId().toString(), tk.getAccess(), tokenExpire, TimeUnit.HOURS);
        model.setLastLoginDate(new Date());
        userMapper.updateById(model);
        return setPermissionInfo(tk);
    }

    public Object getCaptcha() {
        return null;
    }

    /**
     * 网关token过滤器
     * @param token
     * @param permissionCode
     * @return
     */
    public Boolean hasPermission(String token, String permissionCode) {
        if (StringUtils.isEmpty(token)) {
            throw new AuthenticationException("令牌错误");
        }
        token = TokenUtil.cutTokenBearer(token);
        Map<String, Claim> map;
        try {
            map = TokenUtil.verifyToken(token);
        } catch (Exception e) {
            throw new AuthenticationException("令牌过期,请重新登录");
        }
        Long userId = map.get("userId").asLong();
        if (org.springframework.util.StringUtils.isEmpty(redis.get(userId.toString()))) {
            throw new AuthenticationException("令牌过期,请重新登录");
        }
        SysUser user = this.userMapper.selectOne(new QueryWrapper<SysUser>().eq("id", userId).eq("status", SysUser.ENABLE));
        if (user == null) {
            throw new AuthenticationException("令牌异常,请重新登录");
        } else if (!org.springframework.util.StringUtils.isEmpty(adminAccount) &&
                Arrays.asList(adminAccount.split(",")).contains(user.getName())) {
            if (user.getMultiLogin().equals(SysUser.BANNEDMULTILOGIN)) {
                String redisToken = redis.get(userId.toString());
                if (!token.equals(redisToken)) {
                    throw new AuthenticationException("令牌过期,请重新登录");
                }
            }
            refreshToken(userId, token);
            return true;
        } else {
            if (user.getMultiLogin().equals(SysUser.BANNEDMULTILOGIN)) {
                String redisToken = redis.get(userId.toString());
                if (!token.equals(redisToken)) {
                    throw new AuthenticationException("令牌过期,请重新登录");
                }
            }
            // 接口验证
            List<SysPermission> permissionList = redis.getCacheList(userId + ":permission", SysPermission.class);
            if (permissionList.stream().filter(e -> e.getCode().equals(permissionCode)).count() > 0) {
                // 权限验证通过
                refreshToken(userId, token);
                return true;
            } else {
                throw new AuthenticationException("登录账号无权限,请联系管理员");
            }
        }
    }

    public void register(SysUser user) {
        if (!user.getPassword().equals(user.getConfirmPassword())) {
            throw new FieldException("两次输入密码不一致");
        }
        if (userMapper.selectOne(new QueryWrapper<SysUser>().eq("name", user.getName())) != null) {
            throw new ApiException("账号重复,请重新输入");
        }
        user.setPassword(pe.encode(user.getPassword()));
        userMapper.insert(user);
    }

    public void changePass(SysUser user, HttpServletRequest request) {
        if (!user.getPassword().equals(user.getConfirmPassword())) {
            throw new FieldException("两次输入密码不一致");
        }
        String token = request.getHeader("Authorization");
        Map<String, Claim> map;
        try {
            map = TokenUtil.verifyToken(token);
        } catch (Exception e) {
            throw new AuthenticationException("令牌解析错误");
        }
        SysUser model = userMapper.selectById(map.get("userId").asLong());
        if (model == null) {
            throw new FieldException("账号不存在");
        }
        model.setPassword(pe.encode(user.getPassword()));
        userMapper.updateById(model);
    }


    private void cachePermissionInfo(TokenModel tokenModel) {
        redis.setCacheList(tokenModel.getUserId() + RedisUtil.PERMISSIONTAG, tokenModel.getPermissions(), tokenExpire, TimeUnit.HOURS);
        redis.setCacheList(tokenModel.getUserId() + RedisUtil.MENUTAG, tokenModel.getMenus(), tokenExpire, TimeUnit.HOURS);
    }

    private TokenModel setPermissionInfo(TokenModel tokenModel) {
        // 判断是否是管理员账号
        Boolean flag = Arrays.asList(adminAccount.split(",")).contains(tokenModel.getName());
        tokenModel.setRoles(flag ? null : roleService.getRolesByUser(tokenModel.getUserId()));
        tokenModel.setPermissions(flag ? permissionService.getAllPermissions(null) : (tokenModel.getRoles().isEmpty() ? null :
                permissionService.getPermissionsByUser(tokenModel.getUserId(),
                        tokenModel.getRoles().stream().map(e -> e.getId()).collect(Collectors.toList()))));
        tokenModel.setMenus(flag ? menuMapper.selectList(null) : (tokenModel.getRoles().isEmpty() ? null :
                menuService.getMenuByUser(tokenModel.getUserId(),
                        tokenModel.getRoles().stream().map(e -> e.getId()).collect(Collectors.toList()))));
        if (!flag) {
            cachePermissionInfo(tokenModel);
        }
        return tokenModel;
    }

    private TokenModel createToken(SysUser user) throws Exception {
        TokenModel tk = new TokenModel();
        tk.setAccess(TokenUtil.createToken(user, tokenExpire));
        tk.setUserId(user.getId());
        tk.setName(user.getName());
        tk.setUserName(user.getUserName());
        return tk;
    }

    private TokenModel createToken(SysUser user, String token) {
        TokenModel tk = new TokenModel();
        tk.setAccess(token);
        tk.setUserId(user.getId());
        tk.setName(user.getName());
        tk.setUserName(user.getUserName());
        return tk;
    }

    /**
     * 刷新token时间 续费
     *
     * @param userId
     */
    private void refreshToken(Long userId, String token) {
        redis.put(userId.toString(), token, tokenExpire, TimeUnit.HOURS);
    }
}
